package bold.deposit.aop;

import bold.deposit.useful.Redis;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * redis 缓存方法代理
 * 锁的颗粒太大
 * 需并发测试
 */
@Log4j2
@Aspect
@Component
@Order(2)
public class CacheAspect {

    @Autowired
    private Redis redis;

    private Map<String, Method> methodCache = new ConcurrentHashMap<>();
    /**
     * 同一个操作一个key 操作redis
     */
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Set<String> set = ConcurrentHashMap.newKeySet();

    @Around("@annotation(cache)")
    public Object around(ProceedingJoinPoint point, RedisCache cache) {
        Class targetClass = point.getTarget().getClass();
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = null;
        try {
            String methodSplice = targetClass.getName() + ClassUtils.classNamesToString(ms.getParameterTypes());
            if (methodCache.containsKey(methodSplice)) {
                method = methodCache.get(methodSplice);
            } else {
                //注意方法 修饰符 存在报错
                method = targetClass.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
                methodCache.put(methodSplice, method);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        //todo 防止key重名 全类名_方法名_key
        String signValue = "";
        if (!"".equals(cache.sign())) {
            String[] parameterNames = ms.getParameterNames();
            int i = 0;
            for (String pName : parameterNames) {//todo 不用for循环 可以优化
                if (cache.sign().equals(pName)) {
                    signValue = String.valueOf(point.getArgs()[i]);
                    break;
                }
                ++i;
            }
        }

        StringBuffer sb = new StringBuffer();
        sb.append(targetClass.getName())
                .append("_").append(method.getName())
                .append("_").append(cache.key()).append(signValue);

        String key = sb.toString();


        boolean have;
        readWriteLock.readLock().lock();
        try {
            have = redis.ttl(key); //todo 存在同时操作一个key  需要加锁
        } finally {
            readWriteLock.readLock().unlock();
        }

        Object redisValue;
        if (have) {
            Class returnType = method.getReturnType();
            if (returnType == String.class) {
                redisValue = redis.get(key);
            } else if (returnType == List.class) {
                redisValue = redis.lrange(key);
            } else if (returnType == Map.class) {
                redisValue = redis.hgetAll(key);
            } else {
                throw new RuntimeException(" 缓存方法返回值类型不匹配！！");
            }
            return redisValue;
        }

        Object result;
        try {
            result = point.proceed(); //TODO 先调用 Before  在执行方法本体 最后得到 返回值
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException("目标方法查询失败" + method.getName());
        }

        if (result == null) {
            return null;
        }

        int time = cache.time();
        TimeUnit timeUnit = cache.unit();

        readWriteLock.writeLock().lock();
        try {
            if (result instanceof String) {
                redis.set(key, (String) result, time, timeUnit);
            } else if (result instanceof List) {
                List<String> list = (List<String>) result;
                redis.lpush(key, list.toArray(new String[list.size()]));
            } else if (result instanceof Map) {
                redis.hmset(key, (Map<String, String>) result);
            } else {
                throw new RuntimeException("redis 缓存方法 数据返回异常");
            }
        } finally {
            readWriteLock.writeLock().unlock();
        }
        return result;
    }

    public static void main(String[] args) {
        Set<String> set = ConcurrentHashMap.newKeySet();
        set.add("1");
        set.add("1");
        set.add("2");
        System.out.println(set.contains("1"));

        System.out.println(set.toString());
    }
}
